#region License
//-----------------------------------------------------------------------------
// Copyright (c) 2008, Aaron MacDougall, Daniel Jeffery
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of Aaron MacDougall or Daniel Jeffery nor the names of its contributors may
//   be used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
#endregion


#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

using Agro2DPipeline;
using System.Diagnostics;
using System.Reflection;
#if WINDOWS
using System.Windows.Forms;
#endif
#endregion

namespace Agro2D
{
    /// <summary>
    /// The intention of the LevelManager is to load a list of available levels and their associated data filename
    /// Each registed level can be referred to by name or index. A level can be loaded (ie. initialised/loaded into memory)
    /// by name or index.
    ///    
    /// Examples of custom Level data:
    /// Spawn points, Level boundary, Score, Player Lives, Play mode, Difficulty, Camera behaviour
    /// 
    /// Examples of custom player data:
    /// Ammo, lives
    /// </summary>
    public class LevelManager
    {
        #region Fields
        protected bool contentLoaded;

        protected SpriteManager spriteManager;
        protected ContentManager scriptContent;
        protected ContentManager content;
        protected GameInfo gameInfo;

        protected int currentLevelIndex;
        protected Level currentLevel;

        protected string assetName;

        // Levelfactory
        protected Dictionary<string, Level> levelTypes;
        
        #endregion

        #region Properties
        /// <summary>
        /// Returns the number of levels available to be loaded.
        /// </summary>
        public int NumLevels
        {
            get { return gameInfo.LevelFileNames.Count; }
        }

        /// <summary>
        /// The index of the current level.
        /// </summary>
        public int CurrentLevelIndex
        {
            get { return currentLevelIndex; }
        }
        /// <summary>
        /// Returns a reference to the currently active level.
        /// </summary>
        public Level CurrentLevel
        {
            get { return currentLevel; }
        }
        /// <summary>
        /// Returns the name of the currently loaded level.
        /// </summary>
        public string CurrentLevelName
        {
            get { return currentLevel.Name; }
        }


        public List<string> GetLevelNames()
        {
            return this.gameInfo.LevelNames;
        }

        /// <summary>
        /// Returns with in levelEditor or not
        /// </summary>
        public bool InLevelEditor
        {
            get { return spriteManager.InLevelEditor; }
            set { spriteManager.InLevelEditor = value; }
        }
        #endregion

        #region Constructors/Destructors
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="spriteManager">SpriteManager to add Sprites to</param>
        public LevelManager(SpriteManager spriteManager)
        {
            this.spriteManager = spriteManager;
            levelTypes = new Dictionary<string,Level>();
            // Add built-in types
            this.levelTypes.Add("Level", new Level());
        }
        #endregion        

        public void LoadGameFile(string assetName, Game game)
        {
            LoadGameFile(assetName, game.Content, game.Services, null);
        }

        /// <summary>
        /// Load scene from a data file.
        /// </summary>
        /// <param name="filename">data file</param> 
        public virtual void LoadGameFile(string assetName, ContentManager content,
            IServiceProvider serviceProvider, Assembly myAssembly)
        {
            this.assetName = assetName;
            this.content = content;
            this.scriptContent = new ContentManager(serviceProvider);

            gameInfo = content.Load<GameInfo>(assetName);
            CloseLevel();

            // Collision Groups.
            foreach(string coll in this.gameInfo.CollisionGroups.Keys)
            {
                if(!CollisionManager.Instance.GetCollisionGroupIDs().ContainsKey(coll))
                    CollisionManager.Instance.AddCollisionGroupID(coll, (CollisionGroups)Enum.Parse(typeof(CollisionGroups),
                    this.gameInfo.CollisionGroups[coll], true));
            }

            // Register SpriteFactory types.
            SpriteFactory.Instance.LoadSpriteTypesFile(gameInfo.SpriteTemplateFile, content, this.spriteManager, null);

            Log.Instance.LogMessage("LevelManager.LoadGameFile: Game file: \"" + assetName + "\" loaded");

            this.scriptContent.Unload();
            contentLoaded = true;
        }
        /// <summary>
        /// Load a level that has been registered. 
        /// </summary>
        /// <param name="name">Name of level</param>
        public void LoadLevel(string name)
        {
            if(!contentLoaded)
            {
                Log.Instance.LogMessage("LevelManager.LoadLevel: Game file has not been loaded");
                return;
            }

            Debug.Assert(gameInfo.LevelFileNames.Contains(name), "Named level has not been specified in the game file");
            if(!gameInfo.LevelFileNames.Contains(name))
            {
                Log.Instance.LogMessage("LevelManager.LoadLevel: level " + name 
                    +  "has not been specified in the game file");
                return;
            }

            LevelInfo levelInfo = this.scriptContent.Load<LevelInfo>(name);
            this.currentLevel = this.CreateLevel(levelInfo.TypeName, levelInfo);
            if(this.CurrentLevel != null)
            {
                this.CurrentLevel.Load(levelInfo);
                this.CurrentLevel.FileName = name;
                currentLevelIndex = gameInfo.LevelFileNames.IndexOf(name);
                Log.Instance.LogMessage("Level: \"" + name + "\" loaded");
            }
            else
            {
                Log.Instance.LogMessage("LevelManager.LoadLevel: level \"" + name
                    + "\" could not be found.");
            }
            GC.Collect();
        }
        /// <summary>
        /// Load a level that has been registered. 
        /// </summary>
        /// <param name="name">index of level</param>
        public void LoadLevel(int index)
        {
            if(!contentLoaded)
            {
                Log.Instance.LogMessage("LevelManager.LoadLevel: Game file has not been loaded");
                return;
            }

            Debug.Assert(gameInfo.LevelFileNames.Count > index, "Index of level is too low");
            if(gameInfo.LevelFileNames.Count <= index)
            {
                Log.Instance.LogMessage("LevelManager.LoadLevel: level index \"" + index
                    + "\" is too high. This level has not been specified in the game file");
                return;
            }

            this.LoadLevel(gameInfo.LevelFileNames[index]);
        }

        /// <summary>
        /// Reloads the scripts for the Game, SpriteTypes and the current level if there is one.
        /// Existing assets will be updated new values.
        /// </summary>
        public void ReloadGameFile()
        {
            scriptContent.Unload();
            if(!contentLoaded)
            {
#if WINDOWS
                MessageBox.Show("LevelManager.ReloadGameFile: Game not loaded; use LoadGameFile() first",
                    "Game not loaded",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
#endif
#if XBOX360
                Debug.Assert(false);
#endif
                
                return;
            }

            contentLoaded = false;

            gameInfo.CollisionGroups.Clear();
            // Reload game file.
            gameInfo = scriptContent.Load<GameInfo>(this.assetName);

            // Collision Groups.
            CollisionManager.Instance.ClearCollisionGroupIDs();
            foreach(string coll in this.gameInfo.CollisionGroups.Keys)
            {
                CollisionManager.Instance.AddCollisionGroupID(coll, (CollisionGroups)Enum.Parse(typeof(CollisionGroups),
                    this.gameInfo.CollisionGroups[coll], true));
            }

            // Register SpriteFactory types.
            SpriteFactory.Instance.ReLoadSpriteTypesFile(gameInfo.SpriteTemplateFile, scriptContent, this.spriteManager);

            Log.Instance.LogMessage("LevelManager.ReloadGameFile: Game file: \"" + assetName + "\" re-loaded");

            LevelInfo levelInfo = this.scriptContent.Load<LevelInfo>(currentLevel.FileName);
            ReloadLevelScript(levelInfo);

            contentLoaded = true;
        }        

        /// <summary>
        /// Reloads the level and its script.
        /// </summary>
        private void ReloadLevelScript(LevelInfo info)
        {
            if(this.currentLevel != null)
            {
                this.currentLevel = this.CreateLevel(info.TypeName, info);
                if(this.CurrentLevel != null)
                {
                    this.currentLevel.Reload(ref info);
                    Log.Instance.LogMessage("Level: \"" + currentLevel.Name + "\" re-loaded");
                }
                else
                {
                    Log.Instance.LogMessage("LevelManager.LoadLevel: level \"" + currentLevel.Name
                        + "\" caused an error being re-loaded.");
                }
                GC.Collect();
               
            }
        }

        /// <summary>
        /// Update the current Level
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime) 
        {
            if(currentLevel != null)
            {
                currentLevel.Update(gameTime);
            }
        }

        /// <summary>
        /// Clears all of the loaded assets that were loaded by the LevelLoader
        /// </summary>
        public void CloseLevel() 
        {
            if(currentLevel != null)
            {
                currentLevel.CloseLevel();
                currentLevel = null;
            }
            GC.Collect();
        }



        /// <summary>
        /// Create a Level. When not loading a standard Level, CreateLevel will attempt to 
        /// create a class from the current assembly. You must pass Namespace.MyLevel.
        /// Note, currently that Level, must be in entry assembly or calling assembly (ie. child class).
        /// </summary>
        /// <returns>Custom Level or null if not found</returns>
        private Level CreateLevel(string qualifiedLevel, LevelInfo levelInfo)
        {
#if NULL
            // Is standard Level? If not load from assembly.
            if(qualifiedLevel == "Agro2D.Level")
                return new Level(spriteManager);
            else
            {
                Type levelType = null;
                // NB: 'GetEntryAssembly'- entry assembly must implement named Level.
                // 'GetCallingAssembly' - in which the calling assembly must contain the Level (ie. child class).
                try
                {
                    levelType = Assembly.GetEntryAssembly().GetType(qualifiedLevel); // <-
                }
                catch
                {
                    levelType = Assembly.GetCallingAssembly().GetType(qualifiedLevel); // <-                    
                }

                bool valid = (levelType != null);
                Debug.Assert(valid, "Invalid type name:" + qualifiedLevel + ". Use Namespace.MyLevel.");
                if(valid)
                    return (Level)Activator.CreateInstance(levelType, new object[] { spriteManager });
                else
                    return null;
            }
#endif

            string nameSpace = qualifiedLevel.Substring(0, qualifiedLevel.IndexOf('.'));
            int lastindex = qualifiedLevel.LastIndexOf('.');
            string typeName = qualifiedLevel.Substring(lastindex + 1);

            Debug.Assert(levelTypes.ContainsKey(typeName));

            Level level =  (Level)levelTypes[typeName].Clone();
            level.SpriteManager = this.spriteManager;
            return level;
        }

        /// <summary>
        /// Register a Level with the Factory so it can be created through CreateLevel later.
        /// This will save you duplicating loading code.
        /// </summary>
        /// <param name="type">The name to give the type you are creating</param>
        /// <param name="obj">The actual Level with which to clone later through CreateLevel</param>
        public void RegisterLevelType(string type, Level level)
        {
            Debug.Assert(!levelTypes.ContainsKey(type));

            if(!levelTypes.ContainsKey(type))
                levelTypes.Add(type, level);
            else
            {
                Log.Instance.LogMessage("LevelManager.RegisterLevelType: type \"" + type
                    + "\" has already been added to LevelManager");
            }
        }

        /// <summary>
        /// Remove a Level from the factory; it won't be able to be instantiated later.
        /// </summary>
        /// <param name="Level">The type of Level with which to remove</param>
        public void RemoveLevelType(Level level)
        {
            Debug.Assert(levelTypes.ContainsKey(level.Name));

            if(levelTypes.ContainsKey(level.Name))
                levelTypes.Remove(level.Name);
            else
            {
                Log.Instance.LogMessage("LevelManager.RemoveLevelType: type \"" + level.Name
                    + "\" does not exist in LevelManager");
            }
        }

        /// <summary>
        /// Remove a Level from the Factory; it won't be able to be instantiated later.
        /// </summary>
        /// <param name="Level">The type of Level with which to remove</param>
        public void RemoveLevelType(string type)
        {

        }

        /// <summary>
        /// Clear all Levels from the Factory.
        /// </summary>
        public void Clear()
        {
            levelTypes.Clear();
        }

        public void UnloadContent()
        {
            if(this.scriptContent != null)
            {
                this.scriptContent.Unload();
                this.scriptContent.Dispose();
            }
            GC.Collect();
        }
    }

    /// <summary>
    /// Reads a content.xnb file that has been written out by AgroLevelPipeLine.
    /// </summary>
    public class GameReader : ContentTypeReader<GameInfo>
    {
        /// <summary>
        /// Takes the Level input, and converts it to a format that can be used to
        /// at runtime.
        /// </summary>
        protected override GameInfo Read(ContentReader input, GameInfo existingInstance)
        {
            GameInfo instance = new GameInfo();

            int collgroupCount = input.ReadInt32();
            for(int i = 0; i < collgroupCount; i++)
            {
                string key = input.ReadString();
                string value = input.ReadString();
                instance.CollisionGroups.Add(key, value);
            }

            instance.SpriteTemplateFile = input.ReadString();

            int levelFileNameCount = input.ReadInt32();
            for(int i = 0; i < levelFileNameCount; i++)
            {
                string value = input.ReadString();
                instance.LevelFileNames.Add(value);
            }

            int levelNameCount = input.ReadInt32();
            for(int i = 0; i < levelNameCount; i++)
            {
                string value = input.ReadString();
                instance.LevelNames.Add(value);
            }  

            return instance;
        }

    }
}
